#include "../../src/detail/local_system_time_impl.hh"
#include "../../src/util/time_system.hh"
#include "../framework/unittest.hh"
#include <chrono>
#include <cstdlib>
#include <cstring>

using namespace kratos::time_system;

FIXTURE_BEGIN(test_time_system)

constexpr static std::time_t time_2010_01_01_00_00_00_UTC =
    1262304000;                                                 // UTC THIRSDAY
static std::time_t time_2010_01_01_00_00_00_CIVIL = 1262275200; // UTC+8, FRIDAY

CASE(TestGetDaysUTC) {
  ASSERT_TRUE(
      1 == TimeSystem::UTC::get_days_utc(time_2010_01_01_00_00_00_UTC,
                                         time_2010_01_01_00_00_00_UTC + 86400));
  ASSERT_TRUE(2 == TimeSystem::UTC::get_days_utc(time_2010_01_01_00_00_00_UTC,
                                                 time_2010_01_01_00_00_00_UTC +
                                                     86400 * 2));
  ASSERT_TRUE(
      0 == TimeSystem::UTC::get_days_utc(time_2010_01_01_00_00_00_UTC,
                                         time_2010_01_01_00_00_00_UTC + 86399));
}

CASE(TestGetDays) {
  ASSERT_TRUE(
      1 == TimeSystem::CIVIL::get_days(time_2010_01_01_00_00_00_CIVIL,
                                       time_2010_01_01_00_00_00_CIVIL + 86400));
  ASSERT_TRUE(2 == TimeSystem::CIVIL::get_days(time_2010_01_01_00_00_00_CIVIL,
                                               time_2010_01_01_00_00_00_CIVIL +
                                                   86400 * 2));
  ASSERT_TRUE(
      0 == TimeSystem::CIVIL::get_days(time_2010_01_01_00_00_00_CIVIL,
                                       time_2010_01_01_00_00_00_CIVIL + 86399));
}

CASE(TestGetTimestampFromDatetime) {
  Datetime dt;
  dt.year = 2010;
  dt.month = 1;
  dt.day = 1;
  ASSERT_TRUE(time_2010_01_01_00_00_00_CIVIL ==
              TimeSystem::CIVIL::get_second_from_datetime(dt));
}

CASE(TestGetTimestampFromDatetimeUTC) {
  Datetime dt;
  dt.year = 2010;
  dt.month = 1;
  dt.day = 1;
  ASSERT_TRUE(time_2010_01_01_00_00_00_UTC ==
              TimeSystem::UTC::get_second_from_datetime_utc(dt));
}

CASE(TestInSameDay) {
  ASSERT_FALSE(TimeSystem::CIVIL::in_same_day(
      time_2010_01_01_00_00_00_CIVIL, time_2010_01_01_00_00_00_CIVIL - 1));
  ASSERT_TRUE(TimeSystem::CIVIL::in_same_day(time_2010_01_01_00_00_00_CIVIL,
                                             time_2010_01_01_00_00_00_CIVIL));
  ASSERT_TRUE(TimeSystem::CIVIL::in_same_day(
      time_2010_01_01_00_00_00_CIVIL, time_2010_01_01_00_00_00_CIVIL + 1));
  ASSERT_FALSE(TimeSystem::CIVIL::in_same_day(
      time_2010_01_01_00_00_00_CIVIL, time_2010_01_01_00_00_00_CIVIL + 86400));
}

CASE(TestInSameDayUTC) {
  ASSERT_FALSE(TimeSystem::UTC::in_same_day_utc(
      time_2010_01_01_00_00_00_UTC, time_2010_01_01_00_00_00_UTC - 1));
  ASSERT_TRUE(TimeSystem::UTC::in_same_day_utc(time_2010_01_01_00_00_00_UTC,
                                               time_2010_01_01_00_00_00_UTC));
  ASSERT_TRUE(TimeSystem::UTC::in_same_day_utc(
      time_2010_01_01_00_00_00_UTC, time_2010_01_01_00_00_00_UTC + 1));
  ASSERT_FALSE(TimeSystem::UTC::in_same_day_utc(
      time_2010_01_01_00_00_00_UTC, time_2010_01_01_00_00_00_UTC + 86400));
}

CASE(TestInSameWeek) {
  ASSERT_TRUE(TimeSystem::CIVIL::in_same_week(time_2010_01_01_00_00_00_CIVIL,
                                              time_2010_01_01_00_00_00_CIVIL));
  ASSERT_TRUE(TimeSystem::CIVIL::in_same_week(
      time_2010_01_01_00_00_00_CIVIL, time_2010_01_01_00_00_00_CIVIL + 86400));
  ASSERT_TRUE(TimeSystem::CIVIL::in_same_week(time_2010_01_01_00_00_00_CIVIL,
                                              time_2010_01_01_00_00_00_CIVIL +
                                                  86400 * 2));
  ASSERT_FALSE(TimeSystem::CIVIL::in_same_week(time_2010_01_01_00_00_00_CIVIL,
                                               time_2010_01_01_00_00_00_CIVIL +
                                                   86400 * 3));
}

CASE(TestInSameWeekUTC) {
  ASSERT_TRUE(TimeSystem::CIVIL::in_same_week(time_2010_01_01_00_00_00_UTC,
                                              time_2010_01_01_00_00_00_UTC));
  ASSERT_TRUE(TimeSystem::CIVIL::in_same_week(
      time_2010_01_01_00_00_00_UTC, time_2010_01_01_00_00_00_UTC + 86400));
  ASSERT_TRUE(TimeSystem::CIVIL::in_same_week(
      time_2010_01_01_00_00_00_UTC, time_2010_01_01_00_00_00_UTC + 86400 * 2));
  ASSERT_FALSE(TimeSystem::CIVIL::in_same_week(time_2010_01_01_00_00_00_UTC,
                                               time_2010_01_01_00_00_00_UTC +
                                                   86400 * 3 - 60 * 60 * 8));
  ASSERT_TRUE(TimeSystem::CIVIL::in_same_week(time_2010_01_01_00_00_00_UTC,
                                              time_2010_01_01_00_00_00_UTC +
                                                  86400 * 3 - 60 * 60 * 8 - 1));
}

CASE(TestDatetime) {
  Datetime dt({2010, 1, 1, 0, 0, 0});
  ASSERT_TRUE(time_2010_01_01_00_00_00_CIVIL == dt.get_second());
  ASSERT_TRUE(time_2010_01_01_00_00_00_UTC == dt.get_second_utc());

  Datetime dt1("2010/01/01 00:00:00");
  ASSERT_TRUE(time_2010_01_01_00_00_00_CIVIL == dt1.get_second());
  ASSERT_TRUE(time_2010_01_01_00_00_00_UTC == dt1.get_second_utc());
  ASSERT_TRUE("2010/01/01 00:00:00" == dt1.to_string());

  auto dt2 = TimeSystem::CIVIL::get_datetime(time_2010_01_01_00_00_00_CIVIL);
  ASSERT_TRUE(time_2010_01_01_00_00_00_CIVIL == dt2.get_second());

  auto dt3 = TimeSystem::UTC::get_datetime_utc(time_2010_01_01_00_00_00_UTC);
  ASSERT_TRUE(time_2010_01_01_00_00_00_UTC == dt3.get_second_utc());
}

CASE(TestGetMillionsecond1) {
  kratos::time::LocalTimeImpl impl;
  ASSERT_TRUE(0 == impl.get_millionsecond());
  ASSERT_TRUE(0 == impl.get_second());
  kratos::time_system::TimeSystem::update();
  ASSERT_TRUE(0 != impl.get_millionsecond());
  ASSERT_TRUE(0 != impl.get_second());
}

CASE(TestGetUtcDiffSecond1) {
  kratos::time::LocalTimeImpl impl;
  ASSERT_TRUE(0 != impl.utc_diff_second());
}

CASE(TestDiffDays1) {
    kratos::time::LocalTimeImpl impl;
    auto t1 = impl.from("2000/01/01 00:00:00");
    auto t2 = impl.from("2000/01/02 00:00:00");
    ASSERT_TRUE(1 == impl.diff_days(t1->elapsed_seconds(), t2->elapsed_seconds()));
    auto d1 = impl.from(t1->elapsed_seconds());
    ASSERT_TRUE(d1->elapsed_seconds() == t1->elapsed_seconds());
}

CASE(TestLocalTimeIsSameDay1) {
    kratos::time::LocalTimeImpl impl;
    auto t1 = impl.from("2000/01/01 00:00:00");
    auto t2 = impl.from("2000/01/02 00:00:00");
    ASSERT_FALSE(impl.in_same_day(t1->elapsed_seconds(), t2->elapsed_seconds()));
}

CASE(TestLocalTimeIsSameDay2) {
    kratos::time::LocalTimeImpl impl;
    auto t1 = impl.from("2000/01/01 00:00:00");
    auto t2 = impl.from("2000/01/01 00:10:00");
    ASSERT_TRUE(impl.in_same_day(t1->elapsed_seconds(), t2->elapsed_seconds()));
}

CASE(TestLocalTimeIsSameWeek1) {
    kratos::time::LocalTimeImpl impl;
    auto t1 = impl.from("2000/01/01 00:00:00");
    auto t2 = impl.from("2000/01/09 00:00:00");
    ASSERT_FALSE(impl.in_same_week(t1->elapsed_seconds(), t2->elapsed_seconds()));
}

CASE(TestLocalTimeIsSameWeek2) {
    kratos::time::LocalTimeImpl impl;
    auto t1 = impl.from("2000/01/01 00:00:00");
    auto t2 = impl.from("2000/01/02 00:10:00");
    ASSERT_TRUE(impl.in_same_week(t1->elapsed_seconds(), t2->elapsed_seconds()));
}

CASE(TestLocalTimeIsSameMonth1) {
    kratos::time::LocalTimeImpl impl;
    auto t1 = impl.from("2000/01/01 00:00:00");
    auto t2 = impl.from("2000/02/01 00:00:00");
    ASSERT_FALSE(impl.in_same_month(t1->elapsed_seconds(), t2->elapsed_seconds()));
}

CASE(TestLocalTimeIsSameMonth2) {
    kratos::time::LocalTimeImpl impl;
    auto t1 = impl.from("2000/01/01 00:00:00");
    auto t2 = impl.from("2000/01/15 00:10:00");
    ASSERT_TRUE(impl.in_same_month(t1->elapsed_seconds(), t2->elapsed_seconds()));
}

CASE(TestNextWeekday1) {
    kratos::time::LocalTimeImpl impl;
    auto t1 = impl.from("2000/01/01 00:00:00");
    ASSERT_TRUE(t1->next_weekday() == kratos::time::WeekdayEnum::Sunday);
}

CASE(TestPrevWeekday1) {
    kratos::time::LocalTimeImpl impl;
    auto t1 = impl.from("2000/01/01 00:00:00");
    ASSERT_TRUE(t1->prev_weekday() == kratos::time::WeekdayEnum::Friday);
}

CASE(TestDaysAtWeek1) {
    kratos::time::LocalTimeImpl impl;
    auto t1 = impl.from("2000/01/01 00:00:00");
    ASSERT_TRUE(t1->days_at_week() == 6);
}

CASE(TestDaysAtWeek2) {
    kratos::time::LocalTimeImpl impl;
    auto t1 = impl.from("2000/01/02 00:00:00");
    ASSERT_TRUE(t1->days_at_week() == 7);
}

CASE(TestDaysAtMonth1) {
    kratos::time::LocalTimeImpl impl;
    auto t1 = impl.from("2000/01/01 00:00:00");
    ASSERT_TRUE(t1->days_at_month() == 1);
}

CASE(TestDaysAtMonth2) {
    kratos::time::LocalTimeImpl impl;
    auto t1 = impl.from("2000/01/03 11:00:10");
    ASSERT_TRUE(t1->days_at_month() == 3);
}

CASE(TestDaysAtYear1) {
    kratos::time::LocalTimeImpl impl;
    auto t1 = impl.from("2000/01/01 00:00:00");
    ASSERT_TRUE(t1->days_at_year() == 1);
}

CASE(TestDaysAtYear2) {
    kratos::time::LocalTimeImpl impl;
    auto t1 = impl.from("2000/01/05 13:13:13");
    ASSERT_TRUE(t1->days_at_year() == 5);
}

CASE(TestIsLastDayOfWeek1) {
    kratos::time::LocalTimeImpl impl;
    auto t1 = impl.from("2000/01/01 00:00:00");
    ASSERT_FALSE(t1->is_last_day_of_week());
}

CASE(TestIsLastDayOfWeek2) {
    kratos::time::LocalTimeImpl impl;
    auto t1 = impl.from("2000/01/02 00:00:00");
    ASSERT_TRUE(t1->is_last_day_of_week());
}

CASE(TestIsLastDayOfWeek3) {
    kratos::time::LocalTimeImpl impl;
    auto t1 = impl.from("2000/01/02 00:01:00");
    ASSERT_TRUE(t1->is_last_day_of_week());
}

CASE(TestIsLastDayOfMonth1) {
    kratos::time::LocalTimeImpl impl;
    auto t1 = impl.from("2000/01/01 00:00:00");
    ASSERT_FALSE(t1->is_last_day_of_month());
}

CASE(TestIsLastDayOfMonth2) {
    kratos::time::LocalTimeImpl impl;
    auto t1 = impl.from("2000/01/31 00:00:00");
    ASSERT_TRUE(t1->is_last_day_of_month());
}

CASE(TestIsLastDayOfMonth3) {
    kratos::time::LocalTimeImpl impl;
    auto t1 = impl.from("2000/01/31 00:01:00");
    ASSERT_TRUE(t1->is_last_day_of_month());
}

CASE(TestIsLastDayOfYear1) {
    kratos::time::LocalTimeImpl impl;
    auto t1 = impl.from("2000/01/01 00:00:00");
    ASSERT_FALSE(t1->is_last_day_of_year());
}

CASE(TestIsLastDayOfYear2) {
    kratos::time::LocalTimeImpl impl;
    auto t1 = impl.from("2000/12/31 00:00:00");
    ASSERT_TRUE(t1->is_last_day_of_year());
}

CASE(TestIsLastDayOfYear3) {
    kratos::time::LocalTimeImpl impl;
    auto t1 = impl.from("2000/12/31 00:01:00");
    ASSERT_TRUE(t1->is_last_day_of_year());
}

FIXTURE_END(test_time_system)
